﻿using AutoMapper;
using FluentValidation;
using Microsoft.AspNetCore.Mvc;
using Nuclear.Models.DbModels;
using Nuclear.Models.DtoModels;
using Nuclear.Services;
using Nuclear.Utils;
using Nuclear.Validations;
using static Nuclear.Models.DtoModels.Errors;
using static Nuclear.Statics;

namespace Nuclear.Controllers;

#pragma warning disable S6580
[ApiController]
[Route("api/consume_code")]
public class ConsumeCodeController(IDbService db,
    IMapper mapper,
    ILogger<ConsumeCodeController> logger) : ControllerBase
{
    private const int RandomCodeLength = 4;
    private const string RandomCodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

    [HttpGet]
    public async Task<GetConsumeCodesResponseDto> GetConsumeCodesAsync(
        [FromQuery(Name = "api_key")] string apiKey,
        [FromQuery] GetConsumeCodesRequestDto request,
        [FromServices] IValidator<GetConsumeCodesRequestDto> validator,
        CancellationToken token = default)
    {
        logger.LogInformation("Getting consume code");
        var result =
            await validator
                .ValidateRequestAsync<GetConsumeCodesRequestDto, GetConsumeCodesResponseDto>(apiKey, request, token);

        if (result is not null)
            return result;

        var system = await db.GetSoftwareSystemAsync(request.system_id, token);
        if (system is null)
            return InvalidSystemId<GetConsumeCodesResponseDto>(request.system_id);

        var code = request.consume_code;
        var consumeCodes = await db.GetConsumeCodeAsync(
            request.system_id,
            request.merchant_id,
            request.product_id,
            request.user_id,
            request.benefits_card_no,
            request.district_id,
            code,
            request.is_used,
            token);
        switch (consumeCodes.Count)
        {
            case 0:
                return new GetConsumeCodesResponseDto();
            default:
                var details = consumeCodes
                    .Select(mapper.Map<ConsumeCodeDetail>).ToArray();
                return new GetConsumeCodesResponseDto(details);
        }
    }

    [HttpPost]
    public async Task<GenerateConsumeCodeResponseDto> GenerateAsync(
        [FromQuery(Name = "api_key")] string apiKey,
        [FromBody] GenerateConsumeCodeRequestDto request,
        [FromServices]IValidator<GenerateConsumeCodeRequestDto> validator,
        CancellationToken token = default)
    {
        logger.LogInformation("Generating consume code");
        var result =
            await validator
                .ValidateRequestAsync<GenerateConsumeCodeRequestDto, GenerateConsumeCodeResponseDto>(apiKey, request, token);

        if (result is not null)
            return result;

        var system = await db.GetSoftwareSystemAsync(request.SystemId, token);
        if (system is null)
            return InvalidSystemId<GenerateConsumeCodeResponseDto>(request.SystemId);

        var validFrom = request.ValidFrom;
        var validTo = request.ValidTo;
        if (validFrom >= validTo)
            return InvalidDateRange<GenerateConsumeCodeResponseDto>();
        var randomCodes = RandomCodeGenerator.Generate(RandomCodeLength, request.Count, RandomCodeChars);
        var consumeCodes = randomCodes
            .Select(code => new ConsumeCode
            {
                SystemId = request.SystemId,
                MerchantId = request.MerchantId,
                DistrictId = request.DistrictId ?? "",
                UserId = request.UserId,
                BenefitsCardNo = request.BenefitsCardNo?? "",
                ProductId = request.ProductId,
                ValidFrom = validFrom,
                ValidTo = validTo,
                Code = request.Prefix + code
            })
            .ToList();

        var codes = consumeCodes.Select(x => x.Code).ToArray();

        if (request.Save)
        {
            try
            {
                await db.AddConsumeCodesAsync(consumeCodes, token);
            }
            catch (Exception e)
            {
                logger.LogError(e, "Error happened during saving consume codes");
                return InternalError<GenerateConsumeCodeResponseDto>();
            }
        }

        return new GenerateConsumeCodeResponseDto(
            request.SystemId, 
            request.MerchantId, 
            request.UserId, 
            request.BenefitsCardNo,
            codes
        );
    }

    [HttpPut]
    public async Task<UseConsumeCodeResponseDto> UseAsync(
        [FromQuery(Name = "api_key")] string apiKey,
        [FromBody] UseConsumeCodeRequestDto request,
        [FromServices] IValidator<UseConsumeCodeRequestDto> validator,
        CancellationToken token = default)
    {
        logger.LogInformation("Using consume code");
        var result =
            await validator
                .ValidateRequestAsync<UseConsumeCodeRequestDto, UseConsumeCodeResponseDto>(apiKey, request, token);

        if (result is not null)
            return result;

        var system = await db.GetSoftwareSystemAsync(request.SystemId, token);
        if (system is null)
            return InvalidSystemId<UseConsumeCodeResponseDto>(request.SystemId);

        var code = request.ConsumeCode;
        var consumeCodes = await db.GetConsumeCodeAsync(
            request.SystemId,
            merchantId: request.MerchantId,
            productId: request.ProductId,
            userId: request.UserId,
            benefitsCardNo: request.BenefitsCardNo?? "",
            code: code,
            token: token);
        switch (consumeCodes.Count)
        {
            case 0:
                return ConsumeCodeNotFound<UseConsumeCodeResponseDto>(code);
            case > 1:
                return DuplicateConsumeCodeFound<UseConsumeCodeResponseDto>(code);

            default:
                var consumeCode = consumeCodes[0];
                if (consumeCode.IsUsed)
                    return ConsumeCodeAlreadyUsed<UseConsumeCodeResponseDto>(code);
                var today = DateOnlyUtils.Today();
                if (consumeCode.ValidTo < today)
                    return ConsumeCodeExpired<UseConsumeCodeResponseDto>(code);
                if (today < consumeCode.ValidFrom)
                    return ConsumeCodeNotActiveYet<UseConsumeCodeResponseDto>(code);
                consumeCode.IsUsed = true;
                var now = DateTime.UtcNow;
                consumeCode.UpdateTime = now;
                consumeCode.UseTime = now;
                try
                {
                    await db.UpdateConsumeCodesAsync(consumeCode, token);
                }
                catch (Exception e)
                {
                    logger.LogError(e, "Error happened during using consume codes");
                    return InternalError<UseConsumeCodeResponseDto>();
                }
                
                return new UseConsumeCodeResponseDto();
        }
    }
}